#include "filesystem/be_filesystem.h"
#include "be_obj_loader.h"

BeObjLoader::BeObjLoader( BeFile& befile )
 :	m_numberVertices(0),
	m_numberNormals(0),
	m_numberTexCoor(0),
	m_numberVIndices(0),
	m_numberTIndices(0),
	m_numberNIndices(0)
{
// 	cerr << "loading obj" << endl;
	m_filename = befile.getFilename();

	gVertices = new btScalar[5000000];
	gNormals = new float[5000000];
	gTexCoor = new float[5000000];
	gVIndices = new int[5000000];
	gTIndices = new int[5000000];
	gNIndices = new int[5000000];
	

// 	cerr << "loading model: " << filename << endl;
	
	std::string line;
	while ( befile.getLine(line) )
	{
		// trim spaces
		while ( parseH.beginMatchesStrip( " ", line ) ) {};
		while ( parseH.beginMatchesStrip( "\t", line ) ) {};
		// remove comment lines
		if ( parseH.beginMatchesStrip( "//", line ) ) line.clear();
		if ( parseH.beginMatchesStrip( "#", line ) ) line.clear();

		if ( line.length() > 1 )
		{
			line.append(" ");

// 				unsigned int type;
			std::string otype = parseH.returnUntillStrip( " ", line );
			if ( !otype.empty() )
			{
				if ( otype == "v" )
				{
					btScalar x(0.), y(0), z(0);
// 						btVector3 v;

					// x
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string xs = parseH.returnUntillStrip( " ", line );
					if ( !xs.empty() ) x = atof( xs.c_str() );

					// y
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string ys = parseH.returnUntillStrip( " ", line );
					if ( !ys.empty() ) y = atof( ys.c_str() );

					// z
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string zs = parseH.returnUntillStrip( " ", line );
					if ( !zs.empty() ) z = atof( zs.c_str() );

					if ( !xs.empty() && !ys.empty() && !zs.empty() )
					{
						gVertices[ m_numberVertices++ ] = x;
						gVertices[ m_numberVertices++ ] = y;
						gVertices[ m_numberVertices++ ] = z;
// 							cerr << "vectors: " << m_numberVertices << endl;
// 							cerr << "x: " << x << endl;
// 							cerr << "y: " << y << endl;
// 							cerr << "z: " << z << endl;
					}
					else
						std::cerr << "this line sucks: " << line << std::endl;
// 						gVertices.push_back(v);
				}

				else if ( otype == "vt" )
				{
					float u, v;

					// u
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string us = parseH.returnUntillStrip( " ", line );
					u = atof( us.c_str() );

					// v
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string vs = parseH.returnUntillStrip( " ", line );
					v = atof( vs.c_str() );

					if ( !us.empty() && !vs.empty() )  // && line == ""
					{
// 							cerr << "got a texture coordinate here" << endl;
						gTexCoor[m_numberTexCoor++] = u;
// 							if ( u < 0.0f )
// 							cerr << "u: " << m_numberTexCoor << ": '" << u << "'" << endl;
						gTexCoor[m_numberTexCoor++] = v;
// 							if ( v < 0.0f )
// 							cerr << "v: " << m_numberTexCoor << ": '" << v << "'" << endl;
					}
				}

				else if ( otype == "vn" )
				{
					float x=0, y=0, z=0;

					// x
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string xs = parseH.returnUntillStrip( " ", line );
					if ( !xs.empty() ) x = atof( xs.c_str() );

					// y
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string ys = parseH.returnUntillStrip( " ", line );
					if ( !ys.empty() ) y = atof( ys.c_str() );

					// z
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					std::string zs = parseH.returnUntillStrip( " ", line );
					if ( !zs.empty() ) z = atof( zs.c_str() );

// 						if ( !xs.empty() && !ys.empty() && !zs.empty() )
// 						{
// 							cerr << "got a normal here" << endl;
// 							cerr << "x: " << x << endl;
// 							cerr << "y: " << y << endl;
// 							cerr << "z: " << z << endl;
						gNormals[ m_numberNormals++ ] = x;
						gNormals[ m_numberNormals++ ] = y;
						gNormals[ m_numberNormals++ ] = z;
// 						}
				}

				else if ( otype == "f" )
				{
					std::string vindex1, vindex2, vindex3, vindex4;
					std::string tindex1, tindex2, tindex3, tindex4;
					std::string nindex1, nindex2, nindex3, nindex4;
					int vi1 = 0, vi2 = 0, vi3 = 0, vi4 = 0;
					int ti1 = 0, ti2 = 0, ti3 = 0, ti4 = 0;
					int ni1 = 0, ni2 = 0, ni3 = 0, ni4 = 0;

// 						cerr << line << endl;
					// 1
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};
					
					std::string s1 = parseH.returnUntillStrip( " ", line );
					vindex1 = parseH.returnUntillStrip( "/", s1 );
					while ( parseH.beginMatchesStrip( " ", s1 ) ) {};
					if ( !vindex1.empty() ) vi1 = atoi( vindex1.c_str() );
					tindex1 = parseH.returnUntillStrip( "/", s1 );
					if ( !tindex1.empty() ) ti1 = atoi( tindex1.c_str() );
					nindex1 = s1;
					if ( !nindex1.empty() ) ni1 = atoi( nindex1.c_str() );

					// 2
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};

					std::string s2 = parseH.returnUntillStrip( " ", line );
					vindex2 = parseH.returnUntillStrip( "/", s2 );
					if ( !vindex2.empty() ) vi2 = atoi( vindex2.c_str() );
					tindex2 = parseH.returnUntillStrip( "/", s2 );
					if ( !tindex2.empty() ) ti2 = atoi( tindex2.c_str() );
					nindex2 = s2;
					if ( !nindex2.empty() ) ni2 = atoi( nindex2.c_str() );

					// 3
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};

					std::string s3 = parseH.returnUntillStrip( " ", line );
					vindex3 = parseH.returnUntillStrip( "/", s3 );
					if ( !vindex3.empty() ) vi3 = atoi( vindex3.c_str() );
					tindex3 = parseH.returnUntillStrip( "/", s3 );
					if ( !tindex3.empty() ) ti3 = atoi( tindex3.c_str() );
					nindex3 = s3;
					if ( !nindex3.empty() ) ni3 = atoi( nindex3.c_str() );

					// 4
					while ( parseH.beginMatchesStrip( " ", line ) ) {};
					while ( parseH.beginMatchesStrip( "\t", line ) ) {};

					std::string s4 = parseH.returnUntillStrip( " ", line );
					vindex4 = parseH.returnUntillStrip( "/", s4 );
					if ( !vindex4.empty() ) vi4 = atoi( vindex4.c_str() );
					tindex4 = parseH.returnUntillStrip( "/", s4 );
					if ( !tindex4.empty() ) ti4 = atoi( tindex4.c_str() );
					nindex4 = s4;
					if ( !nindex4.empty() ) ni4 = atoi( nindex4.c_str() );

// 						cerr << vi1 << endl;
// 						assert(vi1);
// 						assert(viy);
// 						assert(viz);

					
// 						string last = parseH.returnUntillStrip( " ", line );
// 						if ( last.size() > 1 )
// 						{
// 							cerr << "4 faces dammit '" << last << "'" << endl;
// 							exit(1);
// 						}
					
					
					if ( vi1 != 0 && vi2 != 0 && vi3 != 0 )
					{
						// TODO REMOVE T & N NUMS, unneeded, always the same yo

						if ( vi1 > 0 )	gVIndices[m_numberVIndices++] = vi1;
						else		gVIndices[m_numberVIndices++] = (m_numberVertices/3) + vi1 + 1;
						if ( vi2 > 0 )	gVIndices[m_numberVIndices++] = vi2;
						else		gVIndices[m_numberVIndices++] = (m_numberVertices/3) + vi2 + 1;
						if ( vi3 > 0 )	gVIndices[m_numberVIndices++] = vi3;
						else		gVIndices[m_numberVIndices++] = (m_numberVertices/3) + vi3 + 1;

						if ( ti1 > 0 )	gTIndices[m_numberTIndices++] = ti1;
						else		gTIndices[m_numberTIndices++] = (m_numberTexCoor/2) + ti1 + 1;
						if ( ti2 > 0 )	gTIndices[m_numberTIndices++] = ti2;
						else		gTIndices[m_numberTIndices++] = (m_numberTexCoor/2) + ti2 + 1;
						if ( ti3 > 0 )	gTIndices[m_numberTIndices++] = ti3;
						else		gTIndices[m_numberTIndices++] = (m_numberTexCoor/2) + ti3 + 1;

						if ( ni1 > 0 )	gNIndices[m_numberNIndices++] = ni1;
						else		gNIndices[m_numberNIndices++] = (m_numberNormals/3) + ni1 + 1;
						if ( ni2 > 0 )	gNIndices[m_numberNIndices++] = ni2;
						else		gNIndices[m_numberNIndices++] = (m_numberNormals/3) + ni2 + 1;
						if ( ni3 > 0 )	gNIndices[m_numberNIndices++] = ni3;
						else		gNIndices[m_numberNIndices++] = (m_numberNormals/3) + ni3 + 1;

						if ( vi4 != 0 )
						{
							if ( vi4 > 0 )	gVIndices[m_numberVIndices++] = vi4;
							else		gVIndices[m_numberVIndices++] = (m_numberVertices/3) + vi4 + 1;
							if ( vi1 > 0 )	gVIndices[m_numberVIndices++] = vi1;
							else		gVIndices[m_numberVIndices++] = (m_numberVertices/3) + vi1 + 1;
							if ( vi3 > 0 )	gVIndices[m_numberVIndices++] = vi3;
							else		gVIndices[m_numberVIndices++] = (m_numberVertices/3) + vi3 + 1;

							if ( ti4 > 0 )	gTIndices[m_numberTIndices++] = ti4;
							else		gTIndices[m_numberTIndices++] = (m_numberTexCoor/2) + ti4 + 1;
							if ( ti1 > 0 )	gTIndices[m_numberTIndices++] = ti1;
							else		gTIndices[m_numberTIndices++] = (m_numberTexCoor/2) + ti1 + 1;
							if ( ti3 > 0 )	gTIndices[m_numberTIndices++] = ti3;
							else		gTIndices[m_numberTIndices++] = (m_numberTexCoor/2) + ti3 + 1;

							if ( ni4 > 0 )	gNIndices[m_numberNIndices++] = ni4;
							else		gNIndices[m_numberNIndices++] = (m_numberNormals/3) + ni4 + 1;
							if ( ni1 > 0 )	gNIndices[m_numberNIndices++] = ni1;
							else		gNIndices[m_numberNIndices++] = (m_numberNormals/3) + ni1 + 1;
							if ( ni3 > 0 )	gNIndices[m_numberNIndices++] = ni3;
							else		gNIndices[m_numberNIndices++] = (m_numberNormals/3) + ni3 + 1;
						}

					}
// 						cerr << m_numberTIndices<< endl;
					assert ( m_numberNIndices ==  m_numberVIndices && m_numberVIndices == m_numberTIndices  );

				}

				else if ( otype == "mtllib" ) // material library
				{
					std::string materialfile = parseH.returnUntillStrip( " ", line );
					loadMaterialfile( befile.getDirectory(), materialfile );
				}

				else if ( otype == "usemtl" ) // material library
				{
					std::string usematerial = parseH.returnUntillStrip( " ", line );

					matit = matlist.find( usematerial ); // Search for "Test" key
					if ( matit != matlist.end() )
					{
						Facematerial facematerial;

						facematerial.faceindex_first = m_numberVIndices;
						facematerial.faceindex_number = 0;
						facematerial.material = usematerial;
						facematerials.push_back( facematerial );
					}
				}
			}
		}
	}
		
	// calculate facematerial triangle numbers
	for ( unsigned int i=1; i < facematerials.size(); i++ )
	{
		Facematerial* previousFM = &facematerials[ i-1 ];
		previousFM->faceindex_number = facematerials[i].faceindex_first - previousFM->faceindex_first;
	}
	// to calculate the last one we need to use m_numberVIndices
	Facematerial* previousFM = &facematerials[ facematerials.size()-1 ];
	previousFM->faceindex_number = m_numberVIndices - previousFM->faceindex_first;

// 	cerr << "end loading obj" << endl;
}

void BeObjLoader::loadMaterialfile( const std::string& mpath, const std::string& file )
{
	std::string fullfilename(mpath);
	fullfilename.append(file);

	BeFile materialbefile;
	
	if ( BeFilesystem::Instance()->load( materialbefile, fullfilename ) )
	{
		Material* material = 0;
		std::string line;
		while ( materialbefile.getLine(line) )
		{
			// trim spaces
			while ( parseH.beginMatchesStrip( " ", line ) ) {};
			while ( parseH.beginMatchesStrip( "\t", line ) ) {};
			// remove comment lines
			if ( parseH.beginMatchesStrip( "//", line ) ) line.clear();
			if ( parseH.beginMatchesStrip( "#", line ) ) line.clear();

			if ( line.length() > 1 )
			{
				line.append(" ");
	// 				cerr << line << endl;

	// 				unsigned int type;
				std::string otype = parseH.returnUntillStrip( " ", line );
				if ( !otype.empty() )
				{
					if ( otype == "newmtl" )
					{
						std::string name = parseH.returnUntillStrip( " ", line );
	// 						cerr << "new material, name: " << name << endl;
						matlist[name] = Material();
						material = &matlist[name];
					}

					else if ( otype == "map_Kd" )
					{
						std::string texturename = mpath;
// 						texturename.append( "textures/" );
						texturename.append( parseH.returnUntillStrip( " ", line ) );
	// 						matlist[currentname].textureid = Texturestore::Instance()->load( texturename );
						material->m_imagePath=texturename;

	// 						cerr << "material texture: " << texturename  << " ! " << matlist[currentname].textureid << endl << endl;
					}

					else if ( otype == "Ka" )
					{
						const float x = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float y = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float z = atof(parseH.returnUntillStrip( " ", line ).c_str());
						material->m_material.setAmbient(Vector4f(x,y,z,1));
	// 						cerr << x << endl;
	// 						cerr << y << endl;
	// 						cerr << z << endl;
					}

					else if ( otype == "Kd" )
					{
						const float x = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float y = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float z = atof(parseH.returnUntillStrip( " ", line ).c_str());
						material->m_material.setDiffuse(Vector4f(x,y,z,1));
						// 						cerr << x << endl;
						// 						cerr << y << endl;
						// 						cerr << z << endl;
					}
					else if ( otype == "Ks" )
					{
						const float x = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float y = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float z = atof(parseH.returnUntillStrip( " ", line ).c_str());
						material->m_material.setSpecular(Vector4f(x,y,z,1));
						// 						cerr << x << endl;
						// 						cerr << y << endl;
						// 						cerr << z << endl;
					}
					else if( otype == "Ns" )
					{
						const float shininess = atof(parseH.returnUntillStrip( " ", line ).c_str())/1000.0f*128.0f;
	// 						cerr << "ns yo " << Ns << currentname <<  endl;
						material->m_material.setShininess(shininess);
	// 						assert( material.Ns <= 128.0f ); //Ensure its in valid gl range
					}

					else if( otype == "d" || otype == "Tr" )
					{
						//Transparency, so will store in diffuse alpha component
						//Apparently some materials use d, others use Tr
	// 						material.d = atof(parseH.returnUntillStrip( " ", line ).c_str());
						const float alpha = atof(parseH.returnUntillStrip( " ", line ).c_str());
						material->m_material.getDiffuse().setW(alpha);
	// 						assert( material.d <= 1.0f ); //Ensure its in valid gl range
					}
				}
			}
		}
	}
// 		cerr << "loaded" << endl;

	//GL workaround for ambient set to black in obj materials
	for ( matit = matlist.begin(); matit != matlist.end(); matit++ )
		if( matit->second.m_material.getAmbient() == Vector4f(0,0,0,1) )
		{
			matlist[matit->first].m_material.setAmbient( matit->second.m_material.getDiffuse() );
			std::cerr << "model: " << file << " (" << matit->first << ")" << ": Ambient 0, setting to diffuse color" << std::endl;
		}
}

BeObjLoader::~BeObjLoader()
{
	delete[] gVertices;
	delete[] gNormals;
	delete[] gTexCoor;
	delete[] gVIndices;
	delete[] gTIndices;
	delete[] gNIndices;
}


